﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO.Compression;
using System.Text.RegularExpressions;

namespace Framework.Common
{
    public class SocketHttpHelper
    {
        //Accept	        text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
        //Accept-Encoding	gzip, deflate
        //Accept-Language	zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
        //Connection	    keep-alive
        //Cookie	        __utma=226521935.1159748615.1351659090.1360037488.1360041318.199; __utmz=226521935.1360041318.199.132.utmcsr=baidu|utmccn=(organic)|utmcmd=organic|utmctr=Content-Encoding; __gads=ID=14b17a803a2f7b32:T=1351659090:S=ALNI_MZQbsa60isnfMJJK32Se6Bjb6LCmQ; pgv_pvi=4318108672; __c_review=0; __c_last=1351753537374; __c_visitor=1351753537374094; AJSTAT_ok_times=9; bdshare_firstime=1353636001240; lzstat_uv=27382109693783671877|2817822; Hm_lvt_344a52a039cfdc824abef5a80bb62f17=1357710816; KSHY_No=0; Hm_lvt_f365160a668167e3574395822e274238=1359686821; __utmc=226521935
        //Host	            www.cnblogs.com
        //User-Agent	    Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0
        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="rp"></param>
        static void SendRequest(Stream stream, RequestParameter rp)
        {
            Uri uri = new Uri(rp.Url);
            StringBuilder builder = new StringBuilder();
            if (rp.Method == "GET")
            {
                builder.AppendLine("GET " + uri.PathAndQuery + " HTTP/1.1");
            }
            else if (rp.Method == "POST")
            {
                builder.AppendLine(string.Format("POST {0} HTTP/1.1", uri.PathAndQuery));
                builder.AppendLine("Content-Type: " + rp.ContentType);
            }
            builder.AppendLine("Host: " + uri.Host);
            builder.AppendLine("Connection: Close");
            //builder.AppendLine("Accept-Encoding: gzip, deflate");
            if (rp.Method == "GET")
            {
                builder.Append("\r\n");
            }
            else if (rp.Method == "POST")
            {
                builder.AppendLine(string.Format("Content-Length: {0}\r\n", rp.PostData.Length));
                builder.Append(rp.PostData);
            }
            var bytes = rp.Encoding.GetBytes(builder.ToString());
            stream.Write(bytes, 0, bytes.Length);
        }

        #region core
        /// <summary>
        /// 获取内容
        /// </summary>
        /// <param name="rp">RequestParameter</param>
        /// <returns></returns>
        public static bool GetHtml(RequestParameter rp)
        {
            Uri uri = new Uri(rp.Url);
            IPAddress ipAddress = null;
            bool parseSuccess = false;
            if (rp.RequestIp.IsNotBlank())
            {
                parseSuccess = IPAddress.TryParse(rp.RequestIp, out ipAddress);
            }
            if (!parseSuccess)
            {
                IPHostEntry ipHostEntry = Dns.GetHostEntry(uri.Host);
                if (ipHostEntry.AddressList.IsNotNullOrEmpty()) ipAddress = ipHostEntry.AddressList[0];
                else return false;
            }
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, uri.Port);
            using (Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
            {
                socket.Connect(ipEndPoint);
                using (var stream = new NetworkStream(socket))
                {
                    SendRequest(stream, rp);
                    byte[] bytes = stream.ToBytes();
                    var head = GetHead(ref bytes);
                    string contentEncoding;
                    if (head.TryGetValue("Content-Encoding", out contentEncoding))
                    {
                        if (contentEncoding.ToLower() == "gzip")
                        {
                            bytes = GzipDecompress(bytes);
                        }
                        else if (contentEncoding.ToLower() == "deflate")
                        {
                            bytes = DeflateDecompress(bytes);
                        }
                    }

                    #region 判断是否和页面声明的编码相同
                    rp.Text = rp.Encoding.GetString(bytes);
                    var encodeName = rp.Text.RegularTest("<\\s*?meta\\s+?.*?charset\\s*?=(?:\\s*|\"|')([a-z0-9_-]+)(?:\"|'|;|\\s*).*?>", 1);
                    if (encodeName.IsBlank()) return true;
                    try
                    {
                        var newEncoding = Encoding.GetEncoding(encodeName);
                        if (!Equals(rp.Encoding, newEncoding))
                        {
                            rp.Text = newEncoding.GetString(bytes);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogHelper.Fatal("编码转换错误", ex);
                    }
                    return true;

                    #endregion
                }
            }
        }

        private static byte[] GzipDecompress(byte[] bytes)
        {
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                using (var gZipStream = new GZipStream(ms, CompressionMode.Decompress))
                {
                    var tmp = gZipStream.ToBytes();
                    return tmp;
                }
            }
        }

        private static byte[] DeflateDecompress(byte[] bytes)
        {
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                using (var deflateStream = new DeflateStream(ms, CompressionMode.Decompress))
                {
                    var tmp = deflateStream.ToBytes();
                    return tmp;
                }
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static HttpHeadCollection GetHead(ref byte[] bytes)
        {
            var index = 0;
            for (; index < bytes.Length - 4; index++)
            {
                if (bytes[index] == '\r' && bytes[index + 1] == '\n' && bytes[index + 2] == '\r' && bytes[index + 3] == '\n')
                {
                    break;
                }
            }
            index = index + 4;
            string head = Encoding.ASCII.GetString(bytes.Take(index).ToArray());
            if (index < bytes.Length)
            {
                bytes = bytes.Skip(index).ToArray();
            }
            return new HttpHeadCollection(head);
        }

        #endregion

    }

    /// <summary>
    /// 
    /// </summary>
    class HttpHeadCollection : Dictionary<string, string>
    {
        /// <summary>
        /// 
        /// </summary>
        string Head { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string HttpStatusCode { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string HttpStatusDesc { get; set; }

        /// <summary>
        /// 字符编码
        /// </summary>
        public Encoding Charset { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="head"></param>
        public HttpHeadCollection(string head)
        {
            Head = head;
            ParseHead();
        }

        /// <summary>
        /// 
        /// </summary>
        private void ParseHead()
        {
            if (Head.IsNotBlank())
            {
                var array = Head.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                array.Skip(1).Each(i =>
                {
                    var kv = i.Split(':');
                    Add(kv[0].Trim(), kv[1].Trim());
                });
                string charset;
                if (TryGetValue("Content-Type", out charset))
                {
                    string tmp = charset.RegularTest("charset\\s*?=(?:\\s*|\"|')([a-z0-9_-]+)(?:\"|'|;|\\s*)", 1);
                    if (tmp.IsNotBlank()) Charset = Encoding.GetEncoding(tmp);
                }
                Regex regex = new Regex("http/(?:1\\.[0|1])\\s+(?<code>[0-9]+)\\s+(?<desc>[a-z]+)\r\n", RegexOptions.None | RegexOptions.IgnoreCase);
                MatchCollection matches = regex.Matches(array[0]);
                if (matches.Count > 0)
                {
                    var match = matches[0];
                    if (match.Success)
                    {
                        HttpStatusCode = match.Groups["code"].Value;
                        HttpStatusDesc = match.Groups["desc"].Value;
                    }
                }
            }
        }
    }
}